package flexcommon.validators
{
	import flexcommon.utils.StringUtils;
	
	import mx.validators.ValidationResult;
	import mx.validators.Validator;
  
  [ResourceBundle("FlexCommon")]

	/**
	 * <p>This email validator follows the RFC 5322 specification.</p> 
	 * <p>The default EmailValidator that comes with Flex does not follow this
	 * specification and thus might provided incorrect results in real world
	 * usage.</p>
	 * 
	 * <p>For further info please read: <pre>http://en.wikipedia.org/wiki/Email_address</pre></p>
	 * 
	 * <p>Author: Dimitrios Gianninas</p>
	 */  
	public class RFCEmailValidator extends Validator
	{
		private static const ALLOWED_LOCALNAME_CHARS:RegExp 
			= /[^a-zA-Z0-9!#\$%&'\*\+\-.\/=\?\^_`\{\}\|~]/;
		
		private static const ALLOWED_DOMAINNAME_CHARS:RegExp 
			= /[^a-zA-Z0-9\-.\[\]]/;
		
		/**
		 * Default contructor.
		 */
		public function RFCEmailValidator()
		{
			super();
		}
		
		/**
		 * Performs the actual validation of the email address.
		 * @param value the value that is to be validated
		 * @return an empty Array if validation was successful, otherwise an array of ValidationResult objects
		 */
    override protected function doValidation( value:Object ):Array
    {
      var results:Array = super.doValidation( value );
      
      // if there are errors already, then exit
      if( results.length > 0 ) 
      {
        return results;
      }
      
      var email:String = value as String;
      var atPos:int = email.indexOf( "@" );
      
      if( !this.required && StringUtils.isEmpty( email ) )
      {
        return results;
      }
      
      if( atPos == -1 )
      {
        results.push( new ValidationResult( 
                      true, 
                      null, 
                      "missingAt", 
                      resourceManager.getString("FlexCommon","EMAILVAL_MISSING_AT") ) );
        return results;
      }
      
      // split up the email address now into parts
      var localName:String = email.substring( 0, atPos );
      var domainName:String = email.substr( atPos + 1 );
      
      //trace( "localName>> " + localName + " - " + domainName );
      
      if( localName.length == 0 || localName.length > 64 )
      {
        results.push( new ValidationResult( 
                    true, 
                    null, 
                    "tooManyChars", 
                    resourceManager.getString("FlexCommon","EMAILVAL_BEFORE_AT") ) );
        return results;
      }
      
      if( domainName.length == 0 || domainName.length > 255 )
      {
        results.push( new ValidationResult( 
                    true, 
                    null, 
                    "tooManyChars", 
                    resourceManager.getString("FlexCommon","EMAILVAL_AFTER_AT") ) );
        return results;
      }
      
      if( domainName.indexOf( "@" ) != -1 )
      {
        results.push( new ValidationResult( 
                      true, 
                      null, 
                      "tooManyAt", 
                      resourceManager.getString("FlexCommon","EMAILVAL_TOOMANY_AT") ) );
        return results;
      }
      
      if( localName.charAt( 0 ) == "." )
      {
        results.push( new ValidationResult( 
                    true, 
                    null, 
                    "dotFirstChar", 
                    resourceManager.getString("FlexCommon","EMAILVAL_DOT_FIRST") ) );
        return results;
      }
      
      if( localName.match( ALLOWED_LOCALNAME_CHARS ) )
      {
        results.push( new ValidationResult( 
                    true, 
                    null, 
                    "badLocalName", 
                    resourceManager.getString("FlexCommon","EMAILVAL_BADCHARS_BEFORE") ) );
        return results;
      }
      
      if( domainName.match( ALLOWED_DOMAINNAME_CHARS ) )
      {
        results.push( new ValidationResult( 
                    true, 
                    null, 
                    "badDomainName", 
                    resourceManager.getString("FlexCommon","EMAILVAL_BADCHARS_AFTER") ) );
        return results;
      }
      
      var domainPeriodPos:int = domainName.indexOf( "." );
      
      // the domain name must have at least one period 
      // but cannot end with a period
      if( domainPeriodPos == -1
        || domainPeriodPos == 0
        || domainName.substring( domainPeriodPos + 1 ).length == 0 )
      {
        results.push( new ValidationResult( 
                    true, 
                    null, 
                    "badDomainName", 
                    resourceManager.getString("FlexCommon","EMAILVAL_BADDOMAIN") ) );
        return results;
      }
      
      // if the IP address is specified, then validate it
      if( domainName.charAt( 0 ) == "[" 
        && domainName.charAt( domainName.length - 1 ) == "]" )
      {
        if ( !isValidIPAddress( domainName.substring(1, domainName.length - 1) ) )
        {
          results.push( new ValidationResult( 
                      true, 
                      null, 
                      "badDomainName", 
                      resourceManager.getString("FlexCommon","EMAILVAL_BADDOMAIN") ) );
          return results;
        }
      }
      
      return results;
    }
        
    /**
		 * Validate a given IP address
		 * 
		 * If IP domain, then must follow [x.x.x.x] format
		 * or for IPv6, then follow [x:x:x:x:x:x:x:x] or [x::x:x:x] or some
		 * IPv4 hybrid, like [::x.x.x.x] or [0:00::192.168.0.1]
		 *
		 * @private
		 */ 
		private function isValidIPAddress(ipAddr:String):Boolean
		{
			var ipArray:Array = [];
			var pos:int = 0;
			var newpos:int = 0;
			var item:Number;
			var n:int;
			var i:int;
			
			// if you have :, you're in IPv6 mode
			// if you have ., you're in IPv4 mode
			
			if (ipAddr.indexOf(":") != -1)
			{
				// IPv6
				
				// validate by splitting on the colons
				// to make it easier, since :: means zeros, 
				// lets rid ourselves of these wildcards in the beginning
				// and then validate normally
				
				// get rid of unlimited zeros notation so we can parse better
				var hasUnlimitedZeros:Boolean = ipAddr.indexOf("::") != -1;
				if (hasUnlimitedZeros)
				{
					ipAddr = ipAddr.replace(/^::/, "");
					ipAddr = ipAddr.replace(/::/g, ":");
				}
				
				while (true)
				{
					newpos = ipAddr.indexOf(":", pos);
					if (newpos != -1)
					{
						ipArray.push(ipAddr.substring(pos,newpos));
					}
					else
					{
						ipArray.push(ipAddr.substring(pos));
						break;
					}
					pos = newpos + 1;
				}
				
				n = ipArray.length;
				
				const lastIsV4:Boolean = ipArray[n-1].indexOf(".") != -1;
				
				if (lastIsV4)
				{
					// if no wildcards, length must be 7
					// always, never more than 7
					if ((ipArray.length != 7 && !hasUnlimitedZeros) || (ipArray.length > 7))
						return false;
		
					for (i = 0; i < n; i++)
					{
						if (i == n-1)
						{
							// IPv4 part...
							return isValidIPAddress(ipArray[i]);
						}
						
						item = parseInt(ipArray[i], 16);
						
						if (item != 0)
							return false;
					}
				}
				else
				{
				
					// if no wildcards, length must be 8
					// always, never more than 8
					if ((ipArray.length != 8 && !hasUnlimitedZeros) || (ipArray.length > 8))
						return false;
					
					for (i = 0; i < n; i++)
					{
						item = parseInt(ipArray[i], 16);
						
						if (isNaN(item) || item < 0 || item > 0xFFFF)
							return false;
					}
				}
				
				return true;
			}
				
			if (ipAddr.indexOf(".") != -1)
			{
				// IPv4
				
				// validate by splling on the periods
				while (true)
				{
					newpos = ipAddr.indexOf(".", pos);
					if (newpos != -1)
					{
						ipArray.push(ipAddr.substring(pos,newpos));
					}
					else
					{
						ipArray.push(ipAddr.substring(pos));
						break;
					}
					pos = newpos + 1;
				}
				
				if (ipArray.length != 4)
					return false;
	
				n = ipArray.length;
				for (i = 0; i < n; i++)
				{
					item = Number(ipArray[i]);
					if (isNaN(item) || item < 0 || item > 255)
						return false;
				}
				
				return true;
			}
			
			return false;
		}
		
	}
}